#include "instr.hpp"
#include "vm.hpp"
#include "actor.hpp"
#include "exception.hpp"
#include <iostream>
#include <functional>
#include <sstream>
#include <cstring>
#include <stdexcept>

using std::cout;
using std::cin;
using std::endl;
using std::plus;
using std::istringstream;
using std::ostringstream;
using std::out_of_range;

namespace BMD
{
  //! pushes the accumulator onto the top of the stack
  void push_func(VM* vm)
  {
    vm->push();
  }  

  //! reverse the top elements of the stack
  void rev_instr::execute(VM* vm)
  {
    vector<Value> top_stack_elem(rev_num);
    int i = 0;
    try
    {
      vm->get_stack_elements(rev_num, top_stack_elem.begin());
      vm->pop(rev_num);
    }
    catch (out_of_range e)
    {
      // rethrow a detailed exception
      ostringstream error_msg;
      error_msg << "Cannot reverse top " << rev_num
                << "elements, because there aren't enough on the stack.";
      throw out_of_range(error_msg.str());
    }
    
    // push them back in reverse order
    for (i = 0; i < rev_num; ++i)
      vm->push(top_stack_elem[i]);
  }

  //! reads from standard input into the accumulator
  void read_func(VM* vm)
  {
    string s;
    cin >> s;
    vm->set_accumulator(Value(s));
  }

  //! print to the actor's output
  void print_func(VM* vm)
  {
    cout << vm->get_accumulator();
  }
  
  //! print to the actor's output stream, w/ a newline
  void println_func(VM* vm)
  {
    cout << vm->get_accumulator() << endl;
  }

  //! swaps the accumulator and the top of the stack
  void swap_func(VM* vm)
  {
    Value temp = vm->get_accumulator();
    vm->set_accumulator(vm->get_stack_element(0));
    vm->set_stack_element(0, temp);
  }

  //! stops the script from running
  void stop_func(VM* vm)
  {
    vm->stop();
  }

  //! return from a function call by popping the stack frame
  void return_func(VM* vm)
  {
    vm->pop_function_call();
    vm->pop_stack_frame();
  }
  
  //! create a list using the accumulator as the cdr
  //! and the top of the stack as the car
  void cons_func(VM* vm)
  {
    ListRef l = vm->get_accumulator().get_list();
    Value v = vm->get_stack_element(0);
    
    ListRef cons(new List(v, l));
    vm->set_accumulator(Value(cons));
    vm->pop();
  }

  //! retrieve the car of a list
  void car_func(VM* vm)
  {
    ListRef l = vm->get_accumulator().get_nonempty_list();
    vm->set_accumulator(l->get_car());
  }

  //! retrieve the cdr of a list
  void cdr_func(VM* vm)
  {
    ListRef l = vm->get_accumulator().get_nonempty_list();
    vm->set_accumulator(l->get_cdr());
  }
  
  //! puts true (1) in the accumulator iff the value is initialized
  void isnull_func(VM* vm)
  {
    Value acc = vm->get_accumulator();
    vm->set_accumulator(
      Value(boost::apply_visitor(is_type_visitor<NullValue>(), acc)));
  }
  
  //! loads nil into the accumulator
  void nil_func(VM* vm)
  {
    vm->set_accumulator(Value(ListRef()));
  }

  //! stack dump
  void stack_dump_instr::execute(VM* vm)
  {
    cerr << "---" << endl
         << title << endl;
    cerr << "acc:\t";
    vm->get_accumulator().print(cerr, true);
    cerr << endl;
    vector<Value> stack_elem;
    stack_elem.resize(num_elements);
    unsigned int num_retrieved = vm->get_stack_elements_unsafe(num_elements,
                                                               stack_elem.begin());
    for (unsigned int i = 0; i < num_retrieved; ++i)
    {
      cerr << "S" << i << ":\t";
      stack_elem[i].print(cerr, true);
      cerr << endl;
    }
  } 

  //! concatenation class used to implement the concatenation function
  class concat_visitor : public static_visitor<Value>
  {
  public:
    Value operator()(string s1, string s2) const
    {
      // c++ provides string concatenation for us!!!
      return Value(s1 + s2);
    }
    Value operator()(ListRef list1, ListRef list2) const
    {
      Value l = Value(list1);
      Value ret = boost::apply_visitor(clone_visitor(false), l);
      ListRef cdr_find = ret.get_list();

      if (!cdr_find.get())
        return Value(list2);

      // find the last cons cell in l1
      while (cdr_find->get_cdr().get()) 
        cdr_find = cdr_find->get_cdr();

      Value _list2(list2);

      // set it's cdr to l2
      cdr_find->set_cdr(
        boost::apply_visitor(clone_visitor(false), _list2).get_list());
     
      // return the original list
      return ret;
    }
    BINARY_DEFAULT_TO_TYPE_ERROR("concat", "")
  };

  //! concatenate two strings or two lists
  void concat_func(VM* vm)
  {
    Value arg1 = vm->get_accumulator();
    Value arg2 = vm->get_stack_element(0);
    try
    {
      vm->set_accumulator(boost::apply_visitor(concat_visitor(), arg1, arg2));
    }
    catch (type_error e)
    {
      throw type_error("expects two lists or two strings, not a " + arg1.get_type() +
                       (" and a " + arg2.get_type()));
    }
    vm->pop(1);
  }
    
  //! make a block of a given size using a runtime-provided value
  void make_block_instr::execute(VM* vm)
  {
    int num_elements = (tag != Block::Reference) ?
      vm->get_accumulator().get_int() : 1;
    BlockRef new_block(new Block(num_elements, tag));
    vm->set_accumulator(Value(new_block));
  }

  void make_filled_block_instr::execute(VM* vm)
  {
    int num_elements = (tag != Block::Reference) ? 
      vm->get_accumulator().get_int() : 1;
    BlockRef new_block(new Block(num_elements, tag));
    vm->set_accumulator(Value(new_block));
    
    Value fill_elem = vm->pop();

    for (unsigned int i = 0; i < new_block->get_size(); ++i)
    {
      new_block->set_elem(
        i, boost::apply_visitor(clone_visitor(false), fill_elem));
    }
  }

  //! Make a block of predetermined size, using the values provided on the stack.
  void make_block_static_instr::execute(VM* vm)
  {
    // grab the values and remove from the stack
    vector<Value> elems(size);
    vm->get_stack_elements(size, elems.begin());
    vm->pop(size);
    
    // set the accumulator to the newly created block
    vm->set_accumulator(BlockRef(new Block(elems, tag)));
  }

  //! grab an element from an block
  void getelem_NP_func(VM* vm)
  {
    BlockRef block = vm->get_accumulator().get_block();
    int idx = vm->get_stack_element(0).get_int();
    vm->set_accumulator(block->get_elem(idx));
    vm->pop();
  }

  //! set an element in an block
  void setelem_NP_func(VM* vm)
  {
    BlockRef block = vm->get_accumulator().get_block();
    int idx = vm->get_stack_element(0).get_int();
    Value v = vm->get_stack_element(1);
    block->set_elem(idx, v);
    vm->set_accumulator(v);
    vm->pop(2);
  }

  // get the element in the block at the member index
  void getelem_instr::execute(VM* vm)
  {
    BlockRef block = vm->get_accumulator().get_block();
    vm->set_accumulator(block->get_elem(index));
  }

  // set an value in a block at the member index
  void setelem_instr::execute(VM* vm)
  {
    BlockRef block = vm->get_accumulator().get_block();
    Value v = vm->get_stack_element(0);
    block->set_elem(index, v);
    vm->set_accumulator(v);
    vm->pop(1);
  }

  //! get the object of a reference
  void deref_func(VM* vm)
  {
    BlockRef ref = vm->get_accumulator().get_reference();
    vm->set_accumulator(ref->get_elem(0));
  }

  //! set the object of a reference
  void setref_func(VM* vm)
  {
    BlockRef ref = vm->get_accumulator().get_reference();
    Value v = vm->get_stack_element(0);
    ref->set_elem(0, v);
    vm->set_accumulator(v);
    vm->pop(1);
  }

  //! loads a constant into the accumulator
  void const_instr::execute(VM* vm)
  {
    vm->set_accumulator(this->v);
  }

  //! pops the provided number of elements
  void pop_instr::execute(VM* vm)
  {
    vm->pop(num_elements);
  }
  
  /** pushes the stack frame, which is necessary before calling
      functions */
  void push_sf_instr::execute(VM* vm)
  {
    vm->push_stack_frame(ret_addr);
  }

  void assign_instr::execute(VM* vm)
  {
    vm->set_stack_element(pos, vm->get_accumulator());    
  }

  void acc_instr::execute(VM* vm)
  {
    vm->set_accumulator(vm->get_stack_element(pos));
  }

  void call_instr::execute(VM* vm)
  {
    // the stack frame has already been pushed
    // so this does nothing but jump to the correct location
    // adds the function to the call stack
    vm->set_instruction_pointer(func_loc);
    
    // for now, we assume all of the stack elements
    // above the return address are arguments
    vm->push_function_call(func_name, -1, true);
  }

  void apply_instr::execute(VM* vm)
  {
    string func_name = vm->get_accumulator().get_string();

    // grab a function from the actor's function list
    function_info fi = vm->get_actor()->get_function(func_name);
    if (fi.addr == Actor::NO_LABEL)
    {
      ostringstream error_msg;
      error_msg << "function \"" << func_name << "\" does not exist";
      throw runtime_error(error_msg.str());
    }

    // setup the call
    vm->execute(InstructionRef(new call_instr(func_name, fi.addr)));
  }

  void jmp_instr::execute(VM* vm)
  {
    vm->set_instruction_pointer(loc);
  }

  void jz_instr::execute(VM* vm)
  {
    if (vm->get_accumulator().get_int() == 0)
      vm->set_instruction_pointer(loc);
  }

  void jnz_instr::execute(VM* vm)
  {
    if (vm->get_accumulator().get_int() != 0)
      vm->set_instruction_pointer(loc);
  }

  /** handle an RPC assuming the VM has the name. The function returns it's
   * success and it's return value as an out-reference.
   */
  bool rpc_instr::handle_vm_call(VM* vm, Value& ret)
  {  
    bool success = false;
    
    ret = Value();

    // collect the arguments in a function call 
    vector<Value> args;
    vm->get_call_arguments(args);

    VMFunction func = vm->get_function(func_name);
    
    // show that we are calling a function
    vm->push_function_call(func_name, args.size(), false);

    try
    {
      // execute the desired function
      ret = func(args);
      success = true;
    }
    catch (runtime_error e)
    {
    }
    
    // return appropriately from the function call
    vm->execute(shared_ptr<Instruction>(new return_instr()));
    
    return success;
  }

  /** handle an RPC, assuming the actor has the function. Only functions
   * annotated with an execution signal are accessible
   */
  bool rpc_instr::handle_actor_call(VM* vm, Value& ret)
  {
    bool success = false;
    
    ret = Value();

    // check for actor call, grabbing the function address 
    Actor* parent_actor = vm->get_actor();
    shared_ptr<Actor> linked_actor = parent_actor->get_linked_actor();

    if (!linked_actor.get())
      return false;

    // find function information
    function_info fi = linked_actor->get_function(func_name);

    // try to find a function with the given name
    if (fi.addr == Actor::NO_LABEL)
      return success;

    // grab the call arguments off the stack
    vector<Value> args;
    vm->get_call_arguments(args);

    // make sure the number of arguments matches
    if (args.size() < fi.num_args)
      return success;

    // note that we are calling a function
    vm->push_function_call(func_name, args.size(), false);

    try
    {
      // set the return value to the function's result 
      ret = linked_actor->call_function(func_name, args);
      success = true;
    }    
    catch (runtime_error e)
    {
      // we don't need to throw exceptions, just signal
      // failure in the return value
    }

    // return from the function call appropriately 
    vm->execute(shared_ptr<Instruction>(new return_instr()));

    return success;
  }    

  void rpc_instr::execute(VM* vm)
  {
    Value ret;
    bool success;

    // check for registered VM call
    if (vm->has_function(func_name))
      success = handle_vm_call(vm, ret);
    // otherwise, assume the actor has it
    else
      success = handle_actor_call(vm, ret);
    
    // push the return value and success rate
    vm->set_accumulator(ret);
    vm->execute(shared_ptr<Instruction>(new push_instr()));
    vm->set_accumulator(Value(success));
  }
  
  void get_field_instr::execute(VM* vm)
  {
    vm->set_accumulator(
      vm->get_actor()->get_field(index));
  }

  void set_field_instr::execute(VM* vm)
  {
    vm->get_actor()->set_field(index, vm->get_accumulator());
  }

/****************************************
 * arithmetic instructions
 ***************************************/

  class is_int_visitor : public boost::static_visitor<bool>
  {
  public:
    bool operator() (int i) const { return true; }
    bool operator() (double d) const { return false; }
  };
}
